package jmpo.ex;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * Есть несколько распространённых стратегий при работе с исключениями:
 *    1) catch and retry;
 *    2) log and rethrow;
 *    3) ignore;
 *    4) suppress and fallback.
 */

/**
 * catch and retry
 * Поймайте в блоке catch исключение, проведите восстановление и попробуйте снова.
 * Обратите внимание, что ваша программа не идёт по <<другой ветке выполнения>>, как 
 * в случае if-else, а находится в цикле, где вы пытаетесь пройти единственно верным
 * путём.
 */
final class CatchAndRetry {
    /**
     * В этой функции мы ожидаем единственный вариант работы:
     *    будет считано целое число.
     * Если считать не удалось, мы сбрасываем ввод и пробуем снова.
     */
    static long readLong(Scanner sc) {
        do {
            try {
                long l = sc.nextLong();
                return l;
            } catch(InputMismatchException e) {
                sc.skip(".*\n");
                continue;
            }
        } while(true);
    }

    static void test() {
        var l = readLong(new Scanner(System.in));
        System.out.format("%d read\n", l);
    }
}

/**
 * log and rethrow
 * Обычно используется если нужно преобразовать исключение в новый тип, который будет понятен на уровне выше.
 * Так же может быть нужно для специального логирования параметров вызова. 
 * Используется в тех ситуациях, когда мы не компетентны решить задачу.
 */
final class LogAndRethrow {
    static long readLong(Scanner sc) {
        do {
            try {
                long l = sc.nextLong();
                return l;
            } catch(InputMismatchException e) {
                sc.skip(".*\n");
                continue;
            } catch(Exception e) {
                // логируем куда-то
                System.out.println("Unrecoverable scanner error");
                throw e;
            }
        } while(true);
    }

    static void test() {
        try {
            var l = readLong(new Scanner(""));
            System.out.format("%d read\n", l);
        } catch(Exception e) {
            System.out.println("Rethrown exception caught: " + e.getClass().toString());
        }
    }
}

/**
 * ignore
 * Самая простая стратегия :) - ничего не делать.
 * Вероятно, вы вообще не расчитываете на возникновение исключений и только
 * код более высокий по стеку вызовов может решать эти задачи.
 */

/**
 * suppress and fallback
 * Обыкновенно, эта стратегия необходима, когда используемый вами код не предоставляет 
 * возможности обойти исключение при некотором вызове, который может провалиться, но 
 * для вас этот провал не является критичными, вы просто можете пойти другим путём.
 * Например, ваша обычная работа связана с базой данных, но если соединения нет, вы 
 * не должны просто прервать программу, но вывести разумную страницу-затычку. 
 * Причём остальные слои вашего приложения не затрагивает сам факт отсутствия соединения,
 * они просто продолжают работать с затычкой.
 */
final class SuppressAndFallback {
    static long readLongOrDefault(Scanner sc, long defaultValue) {
        try {
            long l = sc.nextLong();
            return l;
        } catch(InputMismatchException e) {
            sc.skip(".*\n");
            return defaultValue;
        }
    }
    /**
     * Скорее данный метод следует переписать так:
     */
    static long readLongOrDefaultAlt(Scanner sc, long defaultValue) {
        if (sc.hasNextLong())
            return sc.nextLong();
        return defaultValue;
    }
    /**
     * Обратим внимание, что в методе readLongOrDefaultAlt мы используем стратегию ignore:
     * если что-то пойдёт не так между hasNext и next, то мы не можем быть ответственны за
     * устранение ошибки, так как она, очевидно, гораздо сложнее нашей задачи.
     */

    static void test() {
        var l = readLongOrDefault(new Scanner(""), 0);
        System.out.format("%d read\n", l);
    }
}

public class ExceptionStrategy {
    public static void main(String[] args) {
        if (false) CatchAndRetry.test();
        if (false) LogAndRethrow.test();
    }
}
